#define DEFINITION_CACHE
#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
  /// <summary>
  /// The SignalContext is a helper object for the Signal 
  /// class which are used to compute which signal drives
  /// the Signal instance to which the SignalContext belongs
  /// (a Signal may have a different driver in each cycle).
  /// 
  /// Notice that the SignalContext provides to modes of 
  /// operation: cached and noncached. The former is
  /// typically faster when signal definitions are
  /// highly repetitive across simulation cycles
  /// </summary>
  class SignalContext
  {
    /// <summary>
    /// The DatapathContext of the datapath in which
    /// the owning signal (and thus this instance) is
    /// contained. Used to determine which of the
    /// defining signalflowgraphs are active by
    /// forming the intersection of the two sets
    /// </summary>
    private DatapathContext datapathContext;

    /// <summary>
    /// Id of the Signal instance to which
    /// this SignalContext instance belongs
    /// </summary>
    private readonly Symid leftSignalId;

    /// <summary>
    /// A list of signalflowgraphs which may drive
    /// the Signal to which the SignalContext 
    /// instance belongs
    /// </summary>
    private readonly List<Symid> definingSignalFlowGraphs;

    /// <summary>
    /// Given a defining signalflowgraph, the definingSignals
    /// hash maps from a sfg id to the id of the driving signal
    /// </summary>
    private readonly Dictionary<Symid, AbstractSignal> definingSignals;

    /// <summary>
    /// The simulation to which this
    /// finish directive belongs. Look
    /// up symbols for error messages.
    /// </summary>
    private readonly Simulation simulation;

#if DEFINITION_CACHE
    /// <summary>
    /// Caches the defining signal ids for a set of controlsteps
    /// </summary>
    private readonly int[] controlStepCache;

    /// <summary>
    /// Caches the defining Signal instances for a set of controlsteps
    /// </summary>
    private readonly AbstractSignal[] definingSignalCache;
#endif

    /// <summary>
    /// Constructs a new SignalContext (optionally with a signal 
    /// definition cache depending on compilation settings).
    /// </summary>
    /// <param name="datapathContext"></param>
    /// <param name="leftSignal"></param>
    public SignalContext(Simulation sim, DatapathContext datapathContext, Symid leftSignal)
    {
      this.simulation = sim;
      this.datapathContext = datapathContext;
      this.leftSignalId = leftSignal;
      this.definingSignalFlowGraphs = new List<Symid>();
      this.definingSignals = new Dictionary<Symid, AbstractSignal>();

#if DEFINITION_CACHE
      controlStepCache = new int[32];
      // initialize with -1 to mark cache as empty
      for (int i = 0; i < controlStepCache.Length; i++)
        controlStepCache[i] = -1;
      definingSignalCache = new AbstractSignal[32];
#endif
    }

    /// <summary>
    /// Add a new defining signal which is valid when
    /// the referenced signalflowgraph is active
    /// </summary>
    /// <param name="sfgId">signalflowgraph which must be active</param>
    /// <param name="definer">driving signal for the specified sfg</param>
    internal void NewDefiner(Symid sfgId, AbstractSignal definer)
    {
      if (definingSignalFlowGraphs.Contains(sfgId))
      {
        // can only have one driver for a given signalflowgraph
        throw new ApplicationException("multiple assignment in same sfg");
      }
      else
      {
        definingSignalFlowGraphs.Add(sfgId);
        definingSignals[sfgId] = definer;
      }
    }

    /// <summary>
    /// Called by the owning Signal instance to deterimine
    /// what signal drives it
    /// </summary>
    /// <returns>the driving signal for the current simulation cycle</returns>
    public AbstractSignal ComputeCurrentDefiner()
    {
#if DEFINITION_CACHE
      int k = datapathContext.CurrentControlStep;

      if (controlStepCache[k & 0x1f] != k)
      {
        // definer is not in cache. Do a class lookup
        // by cross-sectioning the set of sfg that define this signal
        // with the set of currently active sfg in the datapath context
        Symid intersectionId;
        DatapathContext.LookupResult res = datapathContext.Lookup(definingSignalFlowGraphs, out intersectionId);

        if (res == DatapathContext.LookupResult.Single)
        {
          AbstractSignal definer = definingSignals[intersectionId];
          // cache result  
          definingSignalCache[k & 0x1f] = definer;
          controlStepCache[k & 0x1f] = datapathContext.CurrentControlStep;
          return definer;
        }
        else if (res == DatapathContext.LookupResult.Multiple)
        {
          throw new SignalDefinerException(
            string.Format(
              "Multiple definitions of signal '{0}' in current simulation cycle",
              simulation.signals[leftSignalId].Name
            )
          );
        }
        else if (res == DatapathContext.LookupResult.None)
        {
          throw new SignalDefinerException(
            string.Format(
              "No definition of signal '{0}' in current simulation cycle",
              simulation.signals[leftSignalId].Name
            )
          );
        }
        else
          throw new ApplicationException(); // should not happen
      }
      else
      {
        // definer is available in cache
        return definingSignalCache[k & 0x1f];
      }
#else
      int k = datapathContext.CurrentControlStep;

      Symid intersectionId;
      DatapathContext.LookupResult res = datapathContext.Lookup(definingSignalFlowGraphs, out intersectionId);
      if (res == DatapathContext.LookupResult.Single)
      {
        return definingSignals[intersectionId];        
      }
      else if (res == DatapathContext.LookupResult.Multiple)
      {
        throw new ApplicationException("multiple definitions of signal in current simulation cycle");
      }
      else if (res == DatapathContext.LookupResult.None)
      {
        throw new ApplicationException("no definition of signal in current simulation cycle");
      }
      else
        throw new ApplicationException(); // should not happen
#endif
    }

    /// <summary>
    /// Checks if the signal has a signalflowgraph which
    /// activates the signal in the current simulation
    /// cycle
    /// </summary>
    /// <returns>true iff signal is defined by an active sfg in this cycle</returns>
    public bool HasDefiner()
    {
#if DEFINITION_CACHE
      int k = datapathContext.CurrentControlStep;

      // do a cache lookup
      if (controlStepCache[k & 0x1f] != k)
      {
        Symid intersectionId;
        DatapathContext.LookupResult res = datapathContext.Lookup(definingSignalFlowGraphs, out intersectionId);

        if (res == DatapathContext.LookupResult.Single)
        {
          AbstractSignal definer = definingSignals[intersectionId];
          definingSignalCache[k & 0x1f] = definer;
          controlStepCache[k & 0x1f] = datapathContext.CurrentControlStep;
          return true;
        }
        else if (res == DatapathContext.LookupResult.Multiple)
          throw new ApplicationException("Multiple definitions of signal");
        else if (res == DatapathContext.LookupResult.None)
          return false;

        return false;
      }
      else
      {
        // definer is in cache
        return true;
      }
#else
      Symid ignored;
      DatapathContext.LookupResult res = datapathContext.Lookup(definingSignalFlowGraphs, out ignored);
      if (res == DatapathContext.LookupResult.Multiple)
        throw new ApplicationException("multiple definitions of signal");
      else if (res == DatapathContext.LookupResult.None)
        return false;
      else if (res == DatapathContext.LookupResult.Single)
        return true; // ok, signal has definer in current cycle
      else
        throw new ApplicationException("internal error");
#endif
    }
  }
}